package main

import (
	"errors"
	"fmt"
	"time"
)

// 把函数存储到一个变量中
var (
	userId   = 100
	userData = func(username string, age int32) string { //这个函数就是个匿名函数,顾名思义,就是没有名字的函数
		return fmt.Sprintf("用户名:%v,年龄:%d", username, age)
	}
)

// 可以基于函数创建一个自定义类型，创建后的类型就是函数类型
type MyUserData func(float64, bool) (result string, err error)

func main() {
	/* 函数的定义 */
	caclResult1 := cacl1(1, 2) // 3
	fmt.Println(caclResult1)

	//声明一个类型为函数类型的变量,直接写在一个已有的函数里面
	var cacl2 = func(a int, b int) int {
		return a + b
	}
	fmt.Println(cacl2(1, 2)) // 3

	fmt.Println(Fun("A", "B", "C", -1)) //15 16 你好

	//多返回值
	if caclResult3, err := cacl3(-1, 2); err == nil {
		fmt.Println(caclResult3)
	} else {
		fmt.Println(err) //a或者b不能小于0
	}
	println("---------------")

	/* 函数中的变长参数 */
	fmt.Println(Sum(1, 2))                // 3
	fmt.Println(Sum(1, 2, 3, 4))          // 10
	fmt.Println(Sum(1, 2, 3, 4, 5, 6, 7)) // 28

	//向可变长函数中传入一个切片,需要加上...
	sli := []int{1, 2, 3, 4, 5}
	fmt.Println(Sum(sli...)) //15

	sl := []int{1, 2, 3}
	sl = myAppend(sl) // no elems to append
	fmt.Println(sl)   // [1 2 3]
	sl = myAppend(sl, 4, 5, 6)
	fmt.Println(sl) // [1 2 3 4 5 6]
	println("---------------")

	//Go 函数可以存储在变量中，也可以作为参数传递给其他函数，可以拥有自己的类型
	fmt.Printf("%T \n", userData) //获取类型: func(string, int32) string
	userInfo := fmt.Sprintf("用户ID: %v, 其它信息: %v", userId, userData("zhangsan", 18))
	fmt.Println(userInfo) //用户ID: 100, 其它信息: 用户名:zhangsan,年龄:18

	//Go 函数可以在函数内创建，也可以在创建后作为函数返回值返回
	getUserFunc("test") //getUserFunc: test ---
	println()
	getUserFunc("test")() //getUserFunc: test --- getUserFunc里面的func: test
	println()
	fmt.Println(timeCost(slowFun)(10))

	/* Go 语言中的函数参数不管使用哪种数据类型都是值传递,是对数据进行一个拷贝, 但是具体分为引用类型和非引用类型 */
	arr1 := [4]int{1, 2, 3, 4}
	fmt.Println(arr1) //[1 2 3 4]
	fun1(arr1)
	fmt.Println(arr1) //[1 2 3 4]
	println()

	s1 := []int{1, 2, 3, 4}
	fmt.Println(s1) // [1 2 3 4]
	fun2(s1)
	fmt.Println(s1) //[100 2 3 4]

	/* 递归函数:计算斐波那契数列 */
	fmt.Println(getFibonacci(12)) //144

	/* 指针运算 */
	var num = 1
	ptr(&num)
	fmt.Println(num) //2
}

// 定义函数的传统方式,指定一个int类型的返回值
func cacl1(a int, b int) int {
	return a + b
}

// 函数支持多返回值
func cacl3(a int, b int) (int, error) {
	if a < 0 || b < 0 {
		return -1, errors.New("a或者b不能小于0")
	}
	return a + b, nil
}

// 为每个返回值声明变量名，这种带有名字的返回值被称为具名返回值
func cacl4(a int, b int) (result int, errMsg string) {
	if a < 0 || b < 0 {
		errMsg = "a或者b不能小于0"
		return
	}
	result = a + b
	return
}

// 多个参数具有相同类型放在一起，可以只写一次类型
func Fun(a, b, c string, d int) (x, y int, z string) {
	x = 15
	y = 16
	z = "你好"
	return
	//return 15, 16, "你好" // 这样也可以
}

// 可变长参数入门,对多个数求和运算
func Sum(ops ...int) int {
	ret := 0
	for _, op := range ops {
		ret += op
	}
	return ret
}

// 对于为变长参数的形参，Go 编译器会将零个或多个实参按一定形式转换为对应的变长形参
func myAppend(sl []int, elems ...int) []int {
	fmt.Printf("%T\n", elems) // 输出变长参数的类型: []int, //变长参数实际上是通过切片来实现的
	if len(elems) == 0 {      //可以用切片的len()获取变长参数的个数
		fmt.Println("no elems to append")
		return sl
	}
	sl = append(sl, elems...)
	return sl
}

// 在函数内创建函数(闭包)
func getUserFunc(username string) func() {
	fmt.Print("getUserFunc: ", username, " --- ")
	return func() {
		fmt.Print("getUserFunc里面的func: ", username)
	}
}

func slowFun(num int) int {
	time.Sleep(time.Second * 1)
	return num * num
}
func timeCost(myFunc func(num int) int) func(num int) int {
	return func(n int) int {
		start := time.Now()
		ret := myFunc(n)
		fmt.Println("time cost: ", time.Since(start).Seconds(), " 秒")
		return ret
	}
}

func fun1(arr [4]int) {
	arr[0] = 100
}

func fun2(sli []int) {
	sli[0] = 100
}

// 斐波那契数列
func getFibonacci(n int) int {
	if n == 1 || n == 2 {
		return 1
	}
	return getFibonacci(n-1) + getFibonacci(n-2)
}

// 指针运算
func ptr(n *int) {
	*n++
}
